﻿Option Explicit On
Option Strict On

Imports Microsoft.Xna.Framework
Imports Microsoft.Xna.Framework.Input

Public Class gamePadClass

    Private pollThread As Threading.Thread
    Private blnPollData As Boolean = True
    Private intPlayerIndex As Integer


    Private Structure ButtonInfo

        Public Enum PressTypes As Integer
            PressAndLongPress = 0   'Has seperate events for short and lond presses
            PressAndRepeat = 1      'Like a keyboard, press and release for a single trigger, hold for continued triggers
            PressAndRelease = 2     'Like a mouse events are raised when the button is pressed (mouse down) and released (mouse up)
        End Enum

        Public Button As Input.Buttons

        Public PressType As PressTypes

        Public Sub New(ByVal ButtonValue As Buttons, ByVal PressTypeValue As PressTypes)
            Button = ButtonValue
            PressType = PressTypeValue
        End Sub

    End Structure

    'Used to define whether left or right tigger or stick is raised
    Public Enum gPadControl As Integer
        Left = 0
        Right = 1
    End Enum

    Public Enum TriggerType As Integer
        ShortPress = 0
        LongPress = 1
        Hold = 2
        Release = 3
    End Enum


    Public Event buttonStateChanged(ByVal Button As Integer, ByVal Type As Integer)
    Public Event thumbStickMoved(ByVal tStick As gPadControl, ByVal StickPositionX As Single, ByVal StickPositionX As Single)
    Public Event triggerMoved(ByVal trigger As gPadControl, ByVal tPosition As Single)
    Public Event pollingStopped()

    Private gPadButtons As New List(Of ButtonInfo)
    Private butPressTime() As DateTime
    Private butTriggerTime() As DateTime

    Private leftStickPos As Vector2
    Private rightStickPos As Vector2
    Private leftTriggerPos As Single
    Private rightTriggerPos As Single

    Private blnTriggersAsButtons As Boolean
    Private blnLeftStickAsButton As Boolean
    Private blnRightStickAsButton As Boolean

    Private intLongPressTimeMS As Integer = 1000
    Private intShortPressRepeatAfterMS As Integer = 500
    Private intShortPressRepeatEveryMS As Integer = 100


    Public Sub New(ByVal intGamepadToMonitor As Integer)

        intPlayerIndex = intGamepadToMonitor

        If GamePad.GetState(CType(intPlayerIndex, PlayerIndex)).IsConnected Then

            gPadButtons.Add(New ButtonInfo(Buttons.A, ButtonInfo.PressTypes.PressAndLongPress))
            gPadButtons.Add(New ButtonInfo(Buttons.B, ButtonInfo.PressTypes.PressAndLongPress))
            gPadButtons.Add(New ButtonInfo(Buttons.X, ButtonInfo.PressTypes.PressAndLongPress))
            gPadButtons.Add(New ButtonInfo(Buttons.Y, ButtonInfo.PressTypes.PressAndLongPress))

            gPadButtons.Add(New ButtonInfo(Buttons.Back, ButtonInfo.PressTypes.PressAndLongPress))
            gPadButtons.Add(New ButtonInfo(Buttons.Start, ButtonInfo.PressTypes.PressAndLongPress))

            gPadButtons.Add(New ButtonInfo(Buttons.LeftShoulder, ButtonInfo.PressTypes.PressAndRepeat))
            gPadButtons.Add(New ButtonInfo(Buttons.RightShoulder, ButtonInfo.PressTypes.PressAndRepeat))

            gPadButtons.Add(New ButtonInfo(Buttons.DPadUp, ButtonInfo.PressTypes.PressAndRepeat))
            gPadButtons.Add(New ButtonInfo(Buttons.DPadLeft, ButtonInfo.PressTypes.PressAndRepeat))
            gPadButtons.Add(New ButtonInfo(Buttons.DPadDown, ButtonInfo.PressTypes.PressAndRepeat))
            gPadButtons.Add(New ButtonInfo(Buttons.DPadRight, ButtonInfo.PressTypes.PressAndRepeat))

            gPadButtons.Add(New ButtonInfo(Buttons.LeftStick, ButtonInfo.PressTypes.PressAndLongPress))
            gPadButtons.Add(New ButtonInfo(Buttons.RightStick, ButtonInfo.PressTypes.PressAndLongPress))

            gPadButtons.Add(New ButtonInfo(Buttons.LeftTrigger, ButtonInfo.PressTypes.PressAndRelease))
            gPadButtons.Add(New ButtonInfo(Buttons.RightTrigger, ButtonInfo.PressTypes.PressAndRelease))

            'gPadButtons.Add(New ButtonInfo(Buttons.LeftThumbstickUp, ButtonInfo.PressTypes.PressAndRepeat))
            'gPadButtons.Add(New ButtonInfo(Buttons.LeftThumbstickDown, ButtonInfo.PressTypes.PressAndRepeat))
            'gPadButtons.Add(New ButtonInfo(Buttons.LeftThumbstickLeft, ButtonInfo.PressTypes.PressAndRepeat))
            'gPadButtons.Add(New ButtonInfo(Buttons.LeftThumbstickRight, ButtonInfo.PressTypes.PressAndRepeat))

            'gPadButtons.Add(New ButtonInfo(Buttons.RightThumbstickUp, ButtonInfo.PressTypes.PressAndRepeat))
            'gPadButtons.Add(New ButtonInfo(Buttons.RightThumbstickDown, ButtonInfo.PressTypes.PressAndRepeat))
            'gPadButtons.Add(New ButtonInfo(Buttons.RightThumbstickLeft, ButtonInfo.PressTypes.PressAndRepeat))
            'gPadButtons.Add(New ButtonInfo(Buttons.RightThumbstickRight, ButtonInfo.PressTypes.PressAndRepeat))

            For Each b As ButtonInfo In gPadButtons

                If b.Button = Buttons.LeftTrigger Or b.Button = Buttons.RightTrigger Then _
                    blnTriggersAsButtons = True

                If b.Button = Buttons.LeftThumbstickUp Or b.Button = Buttons.LeftThumbstickRight Or _
                    b.Button = Buttons.LeftThumbstickDown Or b.Button = Buttons.LeftThumbstickRight Then _
                        blnLeftStickAsButton = True

                If b.Button = Buttons.RightThumbstickUp Or b.Button = Buttons.RightThumbstickRight Or _
                    b.Button = Buttons.RightThumbstickDown Or b.Button = Buttons.RightThumbstickRight Then _
                        blnRightStickAsButton = True

            Next

            ReDim butPressTime(gPadButtons.Count)
            ReDim butTriggerTime(gPadButtons.Count)

        Else
            Throw New Exception("Controller Disconnected")
        End If
    End Sub

    Public Sub startPolling()
        pollThread = New Threading.Thread(AddressOf Poll)
        pollThread.Start()
    End Sub

    Public Sub stopPolling()
        blnPollData = False
    End Sub


    Private Sub Poll()

        Dim gState As GamePadState

        gState = GamePad.GetState(CType(intPlayerIndex, PlayerIndex), GamePadDeadZone.Circular)

        While blnPollData = True And gState.IsConnected = True

            Call checkButtonState(gState)

            If Not blnLeftStickAsButton Then

                Call checkLeftThumbStick(gState.ThumbSticks.Left)

            End If

            If Not blnRightStickAsButton Then

                Call checkRightThumbStick(gState.ThumbSticks.Right)

            End If


            If Not blnTriggersAsButtons Then

                Call checkTriggerPosition(gState)

            End If

            Threading.Thread.Sleep(10)

            gState = GamePad.GetState(CType(intPlayerIndex, PlayerIndex), GamePadDeadZone.Circular)

        End While

        gState = Nothing
        butPressTime = Nothing
        butTriggerTime = Nothing
        gPadButtons = Nothing

        RaiseEvent pollingStopped()

    End Sub


    Private Sub checkButtonState(ByRef gState As GamePadState)

        For i As Integer = 0 To gPadButtons.Count - 1

            If gState.IsButtonDown(gPadButtons(i).Button) Then

                If gPadButtons(i).PressType = ButtonInfo.PressTypes.PressAndLongPress Then
                    'trigger occurs on release if short press or while held if long ###

                    If butPressTime(i).Equals(New DateTime) Then

                        butPressTime(i) = Now

                    ElseIf butPressTime(i).AddSeconds(intLongPressTimeMS) < Now And butTriggerTime(i).Equals(New DateTime) Then

                        Console.WriteLine("LONG PRESS " & gPadButtons(i).Button.ToString)
                        RaiseEvent buttonStateChanged(gPadButtons(i).Button, TriggerType.LongPress)

                        butTriggerTime(i) = Now

                    End If

                ElseIf gPadButtons(i).PressType = ButtonInfo.PressTypes.PressAndRepeat Then
                    'trigger occurs on press and repeats while pressed ###

                    If butPressTime(i).Equals(New DateTime) Then

                        Console.WriteLine("SHORT PRESS " & gPadButtons(i).Button.ToString)
                        RaiseEvent buttonStateChanged(gPadButtons(i).Button, TriggerType.ShortPress)

                        butPressTime(i) = Now

                        butTriggerTime(i) = Now

                    ElseIf butPressTime(i).AddMilliseconds(intShortPressRepeatAfterMS) < Now And butTriggerTime(i).AddMilliseconds(intShortPressRepeatEveryMS) < Now Then

                        Console.WriteLine("QUICK SHORT PRESS " & gPadButtons(i).Button.ToString)
                        RaiseEvent buttonStateChanged(gPadButtons(i).Button, TriggerType.ShortPress)

                        butTriggerTime(i) = Now

                    End If

                ElseIf gPadButtons(i).PressType = ButtonInfo.PressTypes.PressAndRelease Then
                    'trigger occues on press and seperate trigger occurs on release ###

                    If butPressTime(i).Equals(New DateTime) Then

                        Console.WriteLine("HOLD PRESS " & gPadButtons(i).Button.ToString)
                        RaiseEvent buttonStateChanged(gPadButtons(i).Button, TriggerType.Hold)

                        butPressTime(i) = Now

                    End If

                End If


            ElseIf gState.IsButtonUp(gPadButtons(i).Button) Then

                If butPressTime(i).Equals(New DateTime) = False Then 'the button was previously in a pressed state

                    If gPadButtons(i).PressType = ButtonInfo.PressTypes.PressAndLongPress Then
                        'triggers a short press if no long press has been triggered

                        If butTriggerTime(i).Equals(New DateTime) Then 'a long press was not triggered

                            Console.WriteLine("SHORT PRESS " & gPadButtons(i).Button.ToString)
                            RaiseEvent buttonStateChanged(gPadButtons(i).Button, TriggerType.ShortPress)

                        End If

                    ElseIf gPadButtons(i).PressType = ButtonInfo.PressTypes.PressAndRelease Then
                        'release trigger

                        Console.WriteLine("RELEASE PRESS " & gPadButtons(i).Button.ToString)
                        RaiseEvent buttonStateChanged(gPadButtons(i).Button, TriggerType.Release)

                    End If

                    butPressTime(i) = New DateTime

                    butTriggerTime(i) = New DateTime

                End If

            End If

        Next

    End Sub


    Private Sub checkLeftThumbStick(ByVal stick As Vector2)

        If Not stick.X = 0 Or Not stick.Y = 0 Or _
          Not stick = leftStickPos Then

            Console.WriteLine("L-X:" & stick.X)
            Console.WriteLine("L-Y:" & stick.Y)

            leftStickPos = stick

            RaiseEvent thumbStickMoved(gPadControl.Left, stick.X, stick.Y)
        End If

    End Sub


    Private Sub checkRightThumbStick(ByVal stick As Vector2)

        If Not stick.X = 0 Or Not stick.Y = 0 Or _
          Not stick = rightStickPos Then

            Console.WriteLine("R-X:" & stick.X)
            Console.WriteLine("R-Y:" & stick.Y)

            rightStickPos = stick

            RaiseEvent thumbStickMoved(gPadControl.Right, stick.X, stick.Y)

        End If

    End Sub


    Private Sub checkTriggerPosition(ByRef gState As GamePadState)

        If Not gState.Triggers.Left = 0 Or Not gState.Triggers.Left = leftTriggerPos Then
            Console.WriteLine("LeftTrigger:" & gState.Triggers.Left)
            RaiseEvent triggerMoved(gPadControl.Left, gState.Triggers.Left)
            leftTriggerPos = gState.Triggers.Left
        End If

        If Not gState.Triggers.Right = 0 Or Not gState.Triggers.Right = rightTriggerPos Then
            Console.WriteLine("RightTrigger:" & gState.Triggers.Right)
            RaiseEvent triggerMoved(gPadControl.Right, gState.Triggers.Right)
            rightTriggerPos = gState.Triggers.Right
        End If

    End Sub

    Public Sub setVibration(ByVal leftMotor As Single, ByVal rightMotor As Single)

        GamePad.SetVibration(CType(intPlayerIndex, PlayerIndex), leftMotor, rightMotor)

    End Sub

End Class